【图论】 超级源点与超级汇点

目录

一. 基本概念

二. 例题分析

1. HDU2680 Choose the best route

2. Poj1459 Power Network


一. 基本概念

        超级源点跟超级汇点是模拟出来的虚拟点,虚拟点的所有连接边均没有花费。该技巧多用于图中以下情况:

(1)同时有多个源点和多个汇点,建立超级源点和超级汇点

(2)同时有多个源点和一个汇点,建立超级源点

(3)同时有多个汇点和一个源点,建立超级汇点

        我们平时所做的算法多是适用于一个源点到一个汇点或者是一个源点到多个汇点的类型,但是如果出现多个源点对应多个汇点时,我们会不知所措。如果跑多遍算法容易TLE超时,换个思维来想,既然是从多个源点出发到多个汇点,我们能不能建立一个点来代替多个源点/汇点 的效果,而又不影响答案,这就是虚拟点。

二. 例题分析

1. HDU2680 Choose the best route

Problem Description

给出你n个点,m条有向边,每条路都有时间花费,给你一个终点,多个起点,问你从起点到终点的最小时间花费是多少。

        需要使用最短路算法来解决,但是Floyed算法O(n^3)复杂度会超时 ,而Dijkstra算法又只能求单源点最短路,但这里是多源点单汇点。因此我们有以下两种思路:

(1)思路一:逆向跑Dijkstra,以终点当起点。

(2)思路二:既然是多个源点,我们要求这些源点里到汇点最短的。因此我们可以建立一个虚拟的源点,然后连接所有的起点,但是虚拟点的连接路径长度要为0;然后跑从超级源点到汇点这(n+1)个点的最短距离即可!

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef pair<int,int> P;
typedef long long LL;
const int maxn = 40000 + 7;
struct Edge{
    int to,next,val;
}edge[maxn];
int n,m,s,w,tot,head[1010],dist[1010];
bool vis[1010];
void addEdge(int a,int b,int c){
    edge[tot].to = b;edge[tot].val = c;edge[tot].next = head[a];head[a] = tot++;
}
void Dijkstra(int a){
     dist[a] = 0;
     memset(vis,0,sizeof(vis));
     priority_queue<P,vector<P> , greater<P> > que;
     que.push(P(0,a));
     while(!que.empty()){
        P p = que.top();
        que.pop();
        if(vis[p.second])continue;
        vis[p.second] = 1;
        for(int i = head[p.second];~i;i = edge[i].next){
            if(!vis[edge[i].to]&&dist[edge[i].to] > dist[p.second] + edge[i].val){
                dist[edge[i].to] = dist[p.second] + edge[i].val;
                que.push(P(dist[edge[i].to],edge[i].to));
            }
        }
     }
}
int main()
{
    while(scanf("%d%d%d",&n,&m,&s)!=EOF){
        tot = 0;
        memset(head,-1,sizeof(head));
        memset(dist,INF,sizeof(dist));
        for(int i = 0;i<m;i++){
             int a,b,v;
             scanf("%d%d%d",&a,&b,&v);
             addEdge(a,b,v);//有向边
        }
        scanf("%d",&w);
        for(int i = 0;i<w;i++){
            int p;
            scanf("%d",&p);
            addEdge(0,p,0);//超级源点0,连接所有源点,长度为0
        }
        Dijkstra(0);
        if(dist[s]==INF)printf("-1\n");
        else printf("%d\n",dist[s]);
    }
    return 0;
}

2. Poj1459 Power Network

Problem Description

有三种节点:一种是发电厂,只产生电量不消耗电量;一种是用户,只消耗电量不产生电量;一种是变压器,既不产生也不消耗。给出n个节点,m条电路,每条电路都有电量限制。现在问你供电网络中传给用户的最大电量是多少。

        多个发电厂相当于多个源点,多个用户相当于多个汇点,因此题意转化为求从多个源点到多个汇点的最大电量之和。

        基本思路为建立超级源点0,超级汇点n+1,超级源点连接所有的源点,路径容量为发电厂的产电量;所有的汇点连接超级汇点,路径容量为用户的耗电量。然后跑一遍从超级源点到超级汇点的最大流即可!

#include <iostream>
//#include<bits/stdc++.h>
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 100 + 7;
struct Edge{
    int to,next,cap,flow;
}edge[maxn*maxn*2];
int n,np,nc,m,tot,head[maxn],dist[maxn],cur[maxn];
void addEdge(int a,int b,int c){
    edge[tot].to = b;edge[tot].next = head[a];edge[tot].cap = c;edge[tot].flow = 0;head[a] = tot++;
    edge[tot].to = a;edge[tot].next = head[b];edge[tot].cap = 0;edge[tot].flow = 0;head[b] = tot++;
}
bool BFS(int s,int t){//BFS分层图
    memset(dist,0,sizeof(dist));
    dist[s] = 1;
    queue<int> que;
    que.push(s);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(int i = head[u];~i;i = edge[i].next){
            int p = edge[i].to;
            if(!dist[p]&&edge[i].cap > edge[i].flow){
                dist[p] = dist[u] + 1;
                que.push(p);
            }
        }
    }
    if(!dist[t])return false;
    return true;
}
int DFS(int p,int e,int minFlow){//DFS找增广路
    if(p==e||minFlow==0)return minFlow;
    int f = 0;
    for(int &i = cur[p];~i;i = edge[i].next){
        int v = edge[i].to;
        if(dist[v] == dist[p] + 1&&edge[i].cap > edge[i].flow){
             int dis = DFS(v,e,min(minFlow,edge[i].cap - edge[i].flow));
             minFlow-=dis;
             f+=dis;
             edge[i].flow+=dis;
             edge[i^1].flow-=dis;
             if(minFlow==0)break;
        }
    }
    return f;
}
int Dinic(int s,int t){//Dinic算法
    if(s==t)return 0;
    int flow = 0;
    while(BFS(s,t)){
        for(int i = 0;i<=n+1;i++)cur[i] = head[i];//当前弧优化
        int ans = DFS(s,t,INF);
        flow+=ans;
    }
    return flow;
}
int main()
{
    while(scanf("%d%d%d%d",&n,&np,&nc,&m)!=EOF){
        tot = 0;
        memset(head,-1,sizeof(head));
        for(int i = 0;i<m;i++){
            int a,b,v;
            scanf(" (%d,%d)%d",&a,&b,&v);
            addEdge(a+1,b+1,v);//连接普通电路
        }
        for(int i = 0;i<np;i++){
            int a,v;
            scanf(" (%d)%d",&a,&v);
            addEdge(0,a+1,v);//所有源点链接超级源点,容量为供电量
        }
        for(int i = 0;i<nc;i++){
            int a,v;
            scanf(" (%d)%d",&a,&v);
            addEdge(a+1,n+1,v);//所有汇点连接超级汇点,容量为耗电量
        }
        int maxflow = Dinic(0,n+1);//跑最大流
        printf("%d\n",maxflow);
    }
    return 0;
}
  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿阿阿安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值